/*
 * Timezone.cpp
 *
 *  Created on: Oct 21, 2011
 *      Author: thom
 */

#include "StdAfx.h"
#include "Timezone.h"

//a tab separated values list of timezones
static const std::string				TIMEZONE_DIRECTORY		= "./tzdata/";
static const std::string				TIMEZONE_FILENAME		= TIMEZONE_DIRECTORY + "zone.tab";

static std::map<const std::string, Timezone>	timezoneMap;
static std::vector<const std::string>			timezoneNames;

static bool descendingTimes(std::pair<time_t, unsigned int> entry1, std::pair<time_t, unsigned int> entry2){
	return entry1.first > entry2.first;
}

/**
 * return the list of timezone names available for selection
 */
const Timezone  Timezone::getTimezone(const std::string &timezoneName){
	Timezone result = timezoneMap[timezoneName];
	if(result.isEmpty()){
		result.load(timezoneName, result);
		timezoneMap[timezoneName] = result;
	}
	return result;
}

/**
 * return the list of timezone names available for selection
 */
const std::vector<const std::string> Timezone::getTimezoneNames(){
	if(timezoneNames.empty()){
		std::ifstream infile(TIMEZONE_FILENAME);
		if(infile==0){
			std::string msg = "Error opening file:" + TIMEZONE_FILENAME + "\n";
			throw std::runtime_error(msg);
		}

		std::string line;
		while(getline(infile, line)!=0){
			if(line.size() > 0 && line[0] != '#'){
				//find the first tab ending the country code field
				std::string::size_type pos = line.find('\t');
				if(pos != std::string::npos){
					//find the next tab ending the location field
					pos = line.find('\t', pos + 1);
					//increment pos to point to start of timezone name field
					pos++;
					//find the end of this field
					std::string::size_type pos2 = line.find('\t', pos);
					std::string::size_type length = pos2 == std::string::npos?line.length():pos2 - pos;
					//create the name
					std::string tzname = std::string(line, pos, length);
					//add to the vector
					timezoneNames.push_back(tzname);
				}
			}
		}
	}
	return timezoneNames;
}

Timezone::~Timezone() {
}

static const unsigned int		POS_NUM_TZ_INDICATORS			= 20;
static const unsigned int		POS_NUM_STD_WALL_INDICATORS		= POS_NUM_TZ_INDICATORS + 4;
static const unsigned int		POS_NUM_LEAP_SEC_INDICATORS		= POS_NUM_STD_WALL_INDICATORS + 4;
static const unsigned int		POS_NUM_TRANS_TIMES				= POS_NUM_LEAP_SEC_INDICATORS + 4;
static const unsigned int		POS_NUM_LOC_TIME_TYPES			= POS_NUM_TRANS_TIMES + 4;
static const unsigned int		POS_NUM_TZ_ABBR_STRINGS			= POS_NUM_LOC_TIME_TYPES + 4;
static const unsigned int		POS_DATA_SECTION				= POS_NUM_TZ_ABBR_STRINGS + 4;

static P6::INT32 pullInt(char* block, int idx){
	P6::UINT8 byte = block[idx++];
	P6::UINT32 result = byte << 24;
	byte = block[idx++];
	result |= byte << 16;
	byte = block[idx++];
	result |= byte << 8;
	byte = block[idx++];
	result |= byte ;

	return static_cast<P6::INT32>(result);
}

static time_t pullTime(char* block, int idx){
	return pullInt(block, idx);
}

long long Timezone::fromUtc(time_t utc){
	//find the first transition time smaller than the given UTC time
	long long result = utc;
	for(std::vector<std::pair<time_t, unsigned int>>::iterator it = transitionTimesIndex.begin(); it!=transitionTimesIndex.end(); ++it){
		long long transTime = it->first;
		if(result > transTime){
			TransitionTimeInfo ttinfo = transitionTimesInfo[it->second];
			long offset = ttinfo.getGmtOffset();
			result += offset;
			break;
		}
	}
	return result;
}

time_t Timezone::toUtc(long long local){
	//TODO fix this
	return 1;
}

void Timezone::load(const std::string &tzName, Timezone &tz){
	//open the specified timezone file
	std::string file_name = TIMEZONE_DIRECTORY + tzName;
	std::ifstream file (file_name, std::ios::in|std::ios::binary);
	if (file.good() && file.is_open()){
		file.seekg(0, std::ios::end);
		std::ifstream::pos_type file_info = file.tellg();
		unsigned int size = static_cast<int>(file_info);
		if(size < 50){
			throw std::runtime_error("Invalid input file:" + file_name);
		}
		file.seekg(0, std::ios::beg);
		char* memblock = new char[size];
		file.read (memblock, size);
		file.close();

		//read the headers
		P6::UINT32 tz_utc_local_count = pullInt(memblock, POS_NUM_TZ_INDICATORS);
		P6::UINT32 tz_std_wall_count = pullInt(memblock, POS_NUM_STD_WALL_INDICATORS);
		P6::UINT32 tz_leap_sec_count = pullInt(memblock, POS_NUM_LEAP_SEC_INDICATORS);
		P6::UINT32 tz_trans_times_count = pullInt(memblock, POS_NUM_TRANS_TIMES);
		P6::UINT32 tz_loc_time_count = pullInt(memblock, POS_NUM_LOC_TIME_TYPES);
		P6::UINT32 tz_abbr_count = pullInt(memblock, POS_NUM_TZ_ABBR_STRINGS);
		unsigned int pos = POS_DATA_SECTION;

		//read the transition times based on tz_trans_times_count
		std::vector<time_t> transitionTimes(tz_trans_times_count);
		for(unsigned int ix = 0; ix < tz_trans_times_count; ix++){
			transitionTimes[ix] = pullInt(memblock, pos);
			pos+=4;
		}

		//read the index into the transition times info structure based on tz_trans_times_count
		std::vector<unsigned int> transitionTimeIndex(tz_trans_times_count);
		for(unsigned int ix = 0; ix < tz_trans_times_count; ix++){
			P6::UINT8 localTimeType = memblock[pos++];
			transitionTimeIndex[ix] = localTimeType;
		}

		//read the information about each transition based on tz_loc_time_count
		std::vector<TransitionTimeInfo> transitionTimesInfo;
		for(unsigned int ix = 0; ix < tz_loc_time_count; ix++){
			P6::INT32 gmtOffset = pullInt(memblock, pos);
			pos+=4;
			P6::UINT8 isDst = memblock[pos++];
			P6::UINT8 abbrIndex = memblock[pos++];
			//extract the empty object
			TransitionTimeInfo ttInfo(gmtOffset, isDst == 1, abbrIndex);
			transitionTimesInfo.push_back(ttInfo);
		}

		//read the leap second info based on tz_leap_sec_count
		std::vector<LeapSecondsInfo> leapSecondsVector;
		for(unsigned int ix = 0; ix < tz_leap_sec_count; ix++){
			time_t leapTime = pullTime(memblock, pos);
			pos+=sizeof(time_t);
			P6::UINT32 numSecs = pullInt(memblock, pos);
			pos+=4;
			LeapSecondsInfo leapInfo(leapTime, numSecs);
			leapSecondsVector.push_back(leapInfo);
		}

		//read the standard/wall clock indicators based on tz_std_wall_count
		std::vector<P6::UINT8> stdWallIndicators;
		for(unsigned int ix = 0; ix < tz_std_wall_count; ix++){
			P6::UINT8 stdWallInd = memblock[pos++];
			stdWallIndicators.push_back(stdWallInd);
		}

		//read the standard/wall clock indicators based on tz_utc_local_count
		std::vector<P6::UINT8> utcLocalIndicators;
		for(unsigned int ix = 0; ix < tz_utc_local_count; ix++){
			P6::UINT8 utcLocalInd = memblock[pos++];
			utcLocalIndicators.push_back(utcLocalInd);
		}

		delete[] memblock;

		tz.setLeapSecondsVector(leapSecondsVector);
		tz.setStdWallIndicators(stdWallIndicators);
		tz.setTransitionTimesInfo(transitionTimes, transitionTimeIndex, transitionTimesInfo);
		tz.setUtcLocalIndicators(utcLocalIndicators);
	}
	else{
		std::string msg = "Unable to open file:" + file_name;
		std::cout << msg << std::endl;
		throw std::runtime_error(msg);
	}
}

void Timezone::setTransitionTimesInfo(std::vector<time_t> &times, std::vector<unsigned int> &indices, std::vector<TransitionTimeInfo> &newVal){
	transitionTimesInfo = newVal;
	for(unsigned int ix = 0; ix < times.size(); ++ix){
		std::pair<time_t, unsigned int> entry(times[ix], indices[ix]);
		transitionTimesIndex.push_back(entry);
	}
	sort(transitionTimesIndex.begin(), transitionTimesIndex.end(), descendingTimes);
}
